package bancosys.tec.services;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;

import jmine.tec.services.ServicesController;
import jmine.tec.services.io.ServicesReader;

import org.apache.log4j.Logger;

import bancosys.tec.datadigester.DataDigesterController;
import bancosys.tec.datadigester.DigesterBean;
import bancosys.tec.persist.PersistenceController;
import bancosys.tec.services.testcase.RefDBTestCase;
import bancosys.tec.services.tools.FileUtils;

/**
 * Test case para testes que são rodados sobre arquivos.
 * 
 * @author Rafael Volpato (Jun 4, 2007)
 * @author lundberg (refactoring 2009-09-02)
 */
public abstract class IntegrationTestCase extends RefDBTestCase {
    private static final Logger LOG = Logger.getLogger(IntegrationTestCase.class);

    protected static final String PLANILHA_SERVICOS = "INTEGRACAO-PLANILHA";

    protected static final String XML_SERVICOS = "INTEGRACAO-XML";

    protected static final String DEFAULT_ALLOWED_FILES = "(?i).*\\.(xls|xml)$";

    private URL inputURL;

    /**
     * Construtor utilizado para criar uma instancia "vazia" do teste de integracao e obter quais sao os arquivos de test que devem ser
     * executados.
     */
    public IntegrationTestCase() {
        super();
    }

    /**
     * Construtor.
     * 
     * @param name nome do teste.
     */
    public IntegrationTestCase(String name) {
        super(name);
    }

    /**
     * Construtor utilizado para obter uma instancia de um teste de integracao que vai executar o arquivo de teste definido.
     * 
     * @param url arquivo de teste que vai ser executado
     */
    public IntegrationTestCase(URL url) {
        super(url.getFile());
        this.setTestInput(url);
    }

    /**
     * Versão mais comportada para o nome do teste, baseado na url.
     * 
     * @param url url
     * @return String
     */
    protected String testNameFromUrl(URL url) {
        // XXX MUST DIE
        return url.getPath().substring(url.getPath().indexOf("/cenarios/") != -1 ? url.getPath().indexOf("/cenarios/") : 0);
    }

    /**
     * Obtem uma lista de arquivos de teste que devem ser executados baseado nos arquivos/diretorios passados pelas classes concretas
     * atravez do metodo getInputFiles().
     * 
     * @return Lista de arquivos que contem testes e devem ser executados.
     * @throws IOException
     */
    public final List<URL> getFilesToExecute() throws IOException {
        return FileUtils.getInstance().getFilesToExecute(this.getInputFiles(), this.getAllowedFileExtensionsRegex());
    }

    /**
     * Obtém uma string representando a regex que filtra os arquivos a serem executados pelo teste.
     * 
     * @return String
     */
    protected String getAllowedFileExtensionsRegex() {
        return DEFAULT_ALLOWED_FILES;
    }

    /**
     * Método utilitário, verifica se o nome do arquivo está de acordo com uma regex, util para identificar arquivos de determinadas
     * extenções
     * 
     * @param url url
     * @param regex regex
     * @return boolean
     */
    protected boolean matchesExtension(URL url, String regex) {
        return url.getFile().matches(regex);
    }

    /**
     * Verifica se o arquivo é um xls.
     * 
     * @param url url
     * @return boolean
     */
    protected boolean isXls(URL url) {
        return this.matchesExtension(url, ServicesReader.XLS_REGEX);
    }

    /**
     * Verifica se o arquivo é um xml.
     * 
     * @param url url
     * @return boolean
     */
    protected boolean isXml(URL url) {
        return this.matchesExtension(url, ServicesReader.XML_REGEX);
    }

    /**
     * Verifica se o arquivo é um xml.
     * 
     * @param url url
     * @return boolean
     */
    protected boolean isZip(URL url) {
        return this.matchesExtension(url, ServicesReader.ZIP_REGEX);
    }

    /**
     * Executa os testes usando como referencia os testes descritos no datasource.
     * 
     * @exception Throwable if any exception is thrown
     */
    @Override
    protected final void runTest() throws Throwable {
        this.beforeTest();
        try {
            this.doRunTest();
            this.afterTestSuccess();
        } catch (Throwable t) {
            this.afterTestFail(t);
            throw t;
        } finally {
            this.afterTest();
        }
    }

    /**
     * Método deve ser sobrescrito para executar ações antes do teste efetivo.
     */
    protected void beforeTest() throws Throwable {
        LOG.info("Iniciando execucao de teste de integracao: " + this.getName());
    }

    /**
     * Executa o teste.
     * 
     * @throws Throwable Throwable
     */
    protected abstract void doRunTest() throws Throwable;

    /**
     * Metodo que deve ser implementado pelos casos de teste de integracao e que deve retornar uma lista de aquivos/diretorios (presentes no
     * classpath) que contem os testes que devem ser executados.
     * 
     * @return uma lista de aquivos/diretorios (presentes no classpath) que contem os testes que devem ser executados.
     */
    protected abstract List<String> getInputFiles();

    /**
     * Método deve ser sobrescrito para executar ações após um teste efetivo que tenha terminado normalmente (sucesso)
     */
    protected void afterTestSuccess() throws Throwable {
        LOG.info("Teste de integracao executado com sucesso: " + this.getName());
    }

    /**
     * Método que deve ser sobrescrito para executar ações após um teste que tenha falhado.
     * 
     * @param t
     */
    protected void afterTestFail(Throwable t) throws Throwable {
        LOG.error(String.format("Falha na execucao do teste: %s", t.getMessage()), t);
    }

    /**
     * Método chamado sempre que o teste termina, independente se o resultado foi sucesso ou não.
     */
    protected void afterTest() throws Throwable {
        // OK
    }

    /**
     * {@inheritDoc}
     */
    public List<String> validateTest() {
        return Collections.emptyList();
    }

    /**
     * @param inputURL the inputURL
     */
    protected void setTestInput(URL inputURL) {
        this.inputURL = inputURL;
        if (inputURL != null) {
            this.setName(this.testNameFromUrl(inputURL));
        }
    }

    /**
     * @return inputURL
     */
    protected URL getTestInput() {
        return this.inputURL;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean canOptimizeRestartBD() {
        return false;
    }

    /**
     * @return the log
     */
    protected static Logger getLog() {
        return LOG;
    }

    /**
     * DataDigesterController
     * 
     * @return DataDigesterController
     */
    protected final DataDigesterController getDataDigesterController() {
        return (DataDigesterController) this.getHelper().getBeanFactory().getBean("dataDigesterController", DataDigesterController.class);
    }

    /**
     * Manda um arquivo para o data digester com uma transacao em volta.
     * 
     * @param fileCode tipo de arquivo.
     * @param testInput arquivo.
     * @return digester bean.
     * @throws Exception caso algo ocorra de errado.
     */
    protected DigesterBean digestFile(final String fileCode, final URL testInput) throws Exception {
        Callable<DigesterBean> action = new Callable<DigesterBean>() {
            public DigesterBean call() throws Exception {
                return IntegrationTestCase.this.getDataDigesterController().digest(fileCode, testInput.getFile(), testInput.openStream());
            }
        };
        return this.getPersistenceController().executeTransacted(action);
    }

    /**
     * Return the ServicesController
     * 
     * @return the ServicesController
     */
    public final ServicesController getServicesController() {
        return (ServicesController) this.getHelper().getBeanFactory().getBean("servicesController", ServicesController.class);
    }

    /**
     * Return the PersistenceController
     * 
     * @return the PersistenceController
     */
    public final PersistenceController getPersistenceController() {
        return (PersistenceController) this.getHelper().getBeanFactory().getBean("persistenceController", PersistenceController.class);
    }

    /**
     * Obtém o código do arquivo para a infra de processamento.
     * 
     * @param url url
     * @return String
     */
    protected String getFileCode(URL url) {
        if (this.isXls(url)) {
            return PLANILHA_SERVICOS;
        } else if (this.isXml(url)) {
            return XML_SERVICOS;
        } else {
            throw new IllegalStateException("Tipo de arquivo não reconhecido a partir do nome: " + url.getFile());
        }
    }
}
